/*  Starshatter OpenSource Distribution
    Copyright (c) 1997-2004, Destroyer Studios LLC.
    All Rights Reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name "Destroyer Studios" nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

    SUBSYSTEM:    Stars.exe
    FILE:         NetGameClient.cpp
    AUTHOR:       John DiCamillo


    OVERVIEW
    ========
    Network Game Manager class
*/

#include "MemDebug.h"
#include "NetGameClient.h"
#include "NetClientConfig.h"
#include "NetLobby.h"
#include "NetPlayer.h"
#include "NetData.h"
#include "NetUtil.h"
#include "Ship.h"
#include "ShipDesign.h"
#include "Shield.h"
#include "Shot.h"
#include "Sim.h"
#include "SimEvent.h"
#include "Weapon.h"
#include "Element.h"
#include "Explosion.h"
#include "HUDView.h"
#include "RadioView.h"
#include "Instruction.h"
#include "Hangar.h"
#include "FlightDeck.h"
#include "Mission.h"

#include "NetMsg.h"
#include "NetHost.h"
#include "NetLayer.h"
#include "NetPeer.h"

#include "Game.h"
#include "Light.h"

// +--------------------------------------------------------------------+

const int MAX_NET_FPS   = 20;
const int MIN_NET_FRAME = 1000 / MAX_NET_FPS;

const char* FormatGameTime();

// +--------------------------------------------------------------------+

NetGameClient::NetGameClient()
: server_id(0), join_req_time(0)
{
    Print("Constructing NetGameClient\n");

    NetHost me;
    Text    server_name;
    WORD    port = 11101;

    NetClientConfig* ncc = NetClientConfig::GetInstance();
    if (ncc) {
        NetServerInfo* info = ncc->GetSelectedServer();

        if (info) {
            server_name = info->hostname;
            port        = info->gameport;
        }
    }

    if (server_name.length() && port > 0) {
        Print("  '%s' is a client of '%s'\n", me.Name(), server_name.data());
        link = new(__FILE__,__LINE__) NetLink;
        server_id = link->AddPeer(NetAddr(server_name, port));
        SendJoinRequest();
    }
    else if (port == 0) {
        Print("  '%s' invalid game port number %d\n", me.Name(), port);
    }
    else {
        Print("  '%s' is a client without a server\n", me.Name());
    }
}

NetGameClient::~NetGameClient()
{
    link->SendMessage(server_id, NET_QUIT_REQUEST, 0, 0, NetMsg::RELIABLE);

    // wait for message to be delivered before shutting down the link:
    Sleep(500);

    join_backlog.destroy();
}

// +--------------------------------------------------------------------+

void
NetGameClient::SendJoinRequest()
{
    if ((NetLayer::GetTime() - join_req_time) < 5000)
    return;

    if (local_player && local_player->GetElement() && server_id) {
        Print("  sending join request - name: '%s' elem: '%s' index: %d\n",
        player_name.data(),
        local_player->GetElement()->Name().data(),
        local_player->GetElementIndex());

        NetJoinRequest join_req;
        join_req.SetName(player_name);
        join_req.SetPassword(player_pass);
        join_req.SetElement(local_player->GetElement()->Name());
        join_req.SetIndex(local_player->GetElementIndex());

        link->SendMessage(server_id, join_req.Pack(), NetJoinRequest::SIZE, NetMsg::RELIABLE);
        join_req_time = NetLayer::GetTime();

        local_player->SetNetObserver(true);
    }
}

// +--------------------------------------------------------------------+

void
NetGameClient::DoJoinRequest(NetMsg* msg)
{
    if (!msg) return;

    NetJoinRequest join_req;
    if (join_req.Unpack(msg->Data())) {
        Print("Client received Join Request from '%s'\n", join_req.GetName());
    }
}

void
NetGameClient::DoJoinAnnounce(NetMsg* msg)
{
    if (!msg) return;

    Sim* sim = Sim::GetSim();
    if (!sim) return;

    NetJoinAnnounce*  join_ann = new(__FILE__,__LINE__) NetJoinAnnounce;
    bool              saved    = false;

    if (join_ann->Unpack(msg->Data())) {
        DWORD nid       = msg->NetID();
        DWORD oid       = join_ann->GetObjID();
        Text  name      = join_ann->GetName();
        Text  elem_name = join_ann->GetElement();
        Text  region    = join_ann->GetRegion();
        Point loc       = join_ann->GetLocation();
        Point velocity  = join_ann->GetVelocity();
        int   index     = join_ann->GetIndex();
        int   shld_lvl  = join_ann->GetShield();
        join_ann->SetNetID(nid);
        Ship* ship      = 0;
        char  ship_name[128];

        strcpy_s(ship_name, Game::GetText("NetGameClient.no-ship").data());

        if (local_player && player_name == name) {
            HUDView::Message(Game::GetText("NetGameClient.local-accept"), name.data(), local_player->Name());

            objid = oid;
            netid = nid;
            local_player->SetObjID(oid);
            local_player->SetNetObserver(false);
            Observe(local_player);

            SimRegion* rgn = local_player->GetRegion();
            if (rgn && region != rgn->Name()) {
                SimRegion* dst = sim->FindRegion(region);
                if (dst) dst->InsertObject(local_player);
            }

            local_player->MoveTo(loc);
            local_player->SetVelocity(velocity);

            Shield* shield = local_player->GetShield();
            if (shield)
            shield->SetNetShieldLevel(shld_lvl);
        }
        else {
            NetPlayer* remote_player = FindPlayerByObjID(oid);
            if (remote_player) {
                remote_player->SetName(name);
                remote_player->SetObjID(oid);

                if (index > 0)
                sprintf_s(ship_name, "%s %d", elem_name.data(), index);
                else
                sprintf_s(ship_name, "%s", elem_name.data());
            }
            else {
                Element* element = sim->FindElement(elem_name);

                if (element) {
                    ship = element->GetShip(index);
                }
                else {
                    Print("NetGameClient::DoJoinAnnounce() could not find elem %s for player '%s' objid %d\n",
                    elem_name.data(), name.data(), oid);

                    NetUtil::SendElemRequest(elem_name.data());
                }

                if (!ship) {
                    // save it for later:
                    join_backlog.append(join_ann);
                    saved = true;
                }
                else {
                    strcpy_s(ship_name, ship->Name());

                    SimRegion* rgn = ship->GetRegion();
                    if (rgn && region != rgn->Name()) {
                        SimRegion* dst = sim->FindRegion(region);
                        if (dst) dst->InsertObject(ship);
                    }

                    ship->MoveTo(loc);
                    ship->SetVelocity(velocity);

                    Shield* shield = ship->GetShield();
                    if (shield)
                    shield->SetNetShieldLevel(shld_lvl);

                    NetPlayer* remote_player = new(__FILE__,__LINE__) NetPlayer(nid);
                    remote_player->SetName(name);
                    remote_player->SetObjID(oid);
                    remote_player->SetShip(ship);

                    players.append(remote_player);

                    if (name == "Server A.I. Ship") {
                        Print("Remote Player '%s' has joined as '%s' with ID %d\n", name.data(), ship_name, oid);
                    }
                    else {
                        HUDView::Message(Game::GetText("NetGameClient.remote-join").data(), name.data(), ship_name);
                    }
                }
            }
        }
    }

    if (!saved)
    delete join_ann;
}

bool
NetGameClient::DoJoinBacklog(NetJoinAnnounce* join_ann)
{
    bool finished = false;

    if (!join_ann)
    return finished;

    Sim* sim = Sim::GetSim();
    if (!sim)
    return finished;

    DWORD nid       = join_ann->GetNetID();
    DWORD oid       = join_ann->GetObjID();
    Text  name      = join_ann->GetName();
    Text  elem_name = join_ann->GetElement();
    Text  region    = join_ann->GetRegion();
    Point loc       = join_ann->GetLocation();
    Point velocity  = join_ann->GetVelocity();
    int   index     = join_ann->GetIndex();
    int   shld_lvl  = join_ann->GetShield();
    Ship* ship      = 0;
    char  ship_name[128];

    strcpy_s(ship_name, Game::GetText("NetGameClient.no-ship").data());

    if (nid && oid) {
        NetPlayer* remote_player = FindPlayerByObjID(oid);
        if (remote_player) {
            remote_player->SetName(name);
            remote_player->SetObjID(oid);

            if (index > 0)
            sprintf_s(ship_name, "%s %d", elem_name.data(), index);
            else
            sprintf_s(ship_name, "%s", elem_name.data());
        }
        else {
            Element* element = sim->FindElement(elem_name);

            if (element) {
                ship = element->GetShip(index);
            }

            if (ship) {
                strcpy_s(ship_name, ship->Name());

                SimRegion* rgn = ship->GetRegion();
                if (rgn && region != rgn->Name()) {
                    SimRegion* dst = sim->FindRegion(region);
                    if (dst) dst->InsertObject(ship);
                }

                ship->MoveTo(loc);
                ship->SetVelocity(velocity);

                Shield* shield = ship->GetShield();
                if (shield)
                shield->SetNetShieldLevel(shld_lvl);

                NetPlayer* remote_player = new(__FILE__,__LINE__) NetPlayer(nid);
                remote_player->SetName(name);
                remote_player->SetObjID(oid);
                remote_player->SetShip(ship);

                players.append(remote_player);
                finished = true;

                if (name == "Server A.I. Ship") {
                    Print("NetGameClient::DoJoinBacklog() Remote Player '%s' has joined as '%s' with ID %d\n", name.data(), ship_name, oid);
                }
                else {
                    HUDView::Message(Game::GetText("NetGameClient.remote-join").data(), name.data(), ship_name);
                }
            }
        }
    }

    return finished;
}


void
NetGameClient::DoQuitRequest(NetMsg* msg)
{
    if (!msg) return;

    Print("Client received Quit Request from NetID: %08X\n", msg->NetID());
}

void
NetGameClient::DoQuitAnnounce(NetMsg* msg)
{
    if (!msg) return;

    NetQuitAnnounce quit_ann;
    quit_ann.Unpack(msg->Data());

    NetPlayer* player = FindPlayerByObjID(quit_ann.GetObjID());

    if (player) {
        NetPlayer* zombie = players.remove(player);
        
        // return remote ship to ship pool:
        Ship* s = zombie->GetShip();
        if (s) {
            s->SetNetworkControl(0);
            zombie->SetShip(0);
        }

        if (quit_ann.GetDisconnected())
        HUDView::Message(Game::GetText("NetGameClient.remote-discon").data(), zombie->Name());
        else
        HUDView::Message(Game::GetText("NetGameClient.remote-quit").data(), zombie->Name());
        delete zombie;
    }
    else {
        Print("Quit Announce for unknown player %08X disconnected = %d\n", msg->NetID(), quit_ann.GetDisconnected());
    }
}

void
NetGameClient::DoGameOver(NetMsg* msg)
{
    if (!msg) return;

    HUDView::Message(Game::GetText("NetGameClient.game-over").data());
    players.destroy();
    active = false;
}

void
NetGameClient::DoDisconnect(NetMsg* msg)
{
    if (!msg) return;

    HUDView::Message(Game::GetText("NetGameClient.discon-detect").data());
    HUDView::Message(Game::GetText("NetGameClient.please-exit").data());
    players.destroy();
    active = false;
}

void
NetGameClient::DoObjLoc(NetMsg* msg)
{
    if (!msg) return;

    NetObjLoc obj_loc;
    obj_loc.Unpack(msg->Data());

    NetPlayer* player = FindPlayerByObjID(obj_loc.GetObjID());
    if (player)
    player->DoObjLoc(&obj_loc);
}

void
NetGameClient::DoObjDamage(NetMsg* msg)
{
    if (!msg) return;

    NetObjDamage obj_damage;
    obj_damage.Unpack(msg->Data());

    Ship* ship = FindShipByObjID(obj_damage.GetObjID());
    if (ship) {
        Sim*        sim         = Sim::GetSim();
        Shot*       shot        = FindShotByObjID(obj_damage.GetShotID());
        const Ship* owner       = 0;
        const char* owner_name  = "[NET]";

        ship->InflictNetDamage(obj_damage.GetDamage(), shot);

        if (shot && sim) {
            if (shot->Owner()) {
                owner       = shot->Owner();
                owner_name  = owner->Name();
            }

            if (shot->IsMissile()) {
                SimRegion*  region = ship->GetRegion();
                float       scale  = ship->Design()->explosion_scale;

                if (scale <= 0)
                scale = ship->Design()->scale;

                if (owner) {
                    const ShipDesign* owner_design = owner->Design();
                    if (owner_design && owner_design->scale < scale)
                    scale = (float) owner_design->scale;
                }

                sim->CreateExplosion(shot->Location(), Point(), Explosion::SHOT_BLAST, 20.0f * scale, scale, region);
            }

            if (!shot->IsBeam()) {
                if (owner) {
                    ShipStats* stats = ShipStats::Find(owner_name);

                    if (stats) {
                        if (shot->IsPrimary())
                        stats->AddGunHit();
                        else if (shot->Damage() > 0)
                        stats->AddMissileHit();
                    }
                }

                shot->Destroy();
            }
        }
    }
}

void
NetGameClient::DoObjKill(NetMsg* msg)
{
    if (!msg) return;

    NetObjKill obj_kill;
    obj_kill.Unpack(msg->Data());

    Ship* ship = FindShipByObjID(obj_kill.GetObjID());
    if (ship) {
        Ship* killer = FindShipByObjID(obj_kill.GetKillerID());
        Text  killer_name = Game::GetText("NetGameClient.unknown");

        if (killer)
        killer_name = killer->Name();

        // log the kill:
        switch (obj_kill.GetKillType()) {
        default:
        case NetObjKill::KILL_MISC:
            Print("Ship '%s' destroyed (misc) (%s)\n", ship->Name(), FormatGameTime());
            break;

        case NetObjKill::KILL_PRIMARY:
        case NetObjKill::KILL_SECONDARY:
            Print("Ship '%s' killed by '%s' (%s)\n", ship->Name(), killer_name.data(), FormatGameTime());
            break;

        case NetObjKill::KILL_COLLISION:
            Print("Ship '%s' killed in collision with '%s' (%s)\n", ship->Name(), killer_name.data(), FormatGameTime());
            break;

        case NetObjKill::KILL_CRASH:
            Print("Ship '%s' destroyed (crash) (%s)\n", ship->Name(), FormatGameTime());

        case NetObjKill::KILL_DOCK:
            Print("Ship '%s' docked (%s)\n", ship->Name(), FormatGameTime());
        }

        // record the kill in the stats:
        if (killer && obj_kill.GetKillType() != NetObjKill::KILL_DOCK) {
            ShipStats* kstats = ShipStats::Find(killer->Name());
            if (kstats) {
                if (obj_kill.GetKillType() == NetObjKill::KILL_PRIMARY)
                kstats->AddEvent(SimEvent::GUNS_KILL, ship->Name());
                
                else if (obj_kill.GetKillType() == NetObjKill::KILL_SECONDARY)
                kstats->AddEvent(SimEvent::MISSILE_KILL, ship->Name());
            }

            if (killer && killer->GetIFF() != ship->GetIFF()) {
                if (ship->GetIFF() > 0 || killer->GetIFF() > 1)
                kstats->AddPoints(ship->Value());
            }
        }

        ShipStats* killee = ShipStats::Find(ship->Name());
        if (killee) {
            if (obj_kill.GetKillType() == NetObjKill::KILL_DOCK)
            killee->AddEvent(SimEvent::DOCK, killer_name);
            else 
            killee->AddEvent(SimEvent::DESTROYED, killer_name);
        }

        if (obj_kill.GetKillType() == NetObjKill::KILL_DOCK) {
            FlightDeck* deck = killer->GetFlightDeck(obj_kill.GetFlightDeck());
            sim->NetDockShip(ship, killer, deck);
        }
        else {
            ship->InflictNetDamage(ship->Integrity());
            ship->DeathSpiral();

            if (!obj_kill.GetRespawn())
            ship->SetRespawnCount(0);
            else
            ship->SetRespawnLoc(obj_kill.GetRespawnLoc());
        }
    }

    // this shouldn't happen in practice, 
    // but if it does, this is what should be done:
    else {
        Shot* shot = FindShotByObjID(obj_kill.GetObjID());

        if (shot) {
            ::Print("NetGameClient::DoObjKill destroying shot '%s'\n", shot->Name());
            shot->Destroy();
        }
    }
}

void
NetGameClient::DoObjSpawn(NetMsg* msg)
{
}

void
NetGameClient::DoObjHyper(NetMsg* msg)
{
    if (!msg) return;
    Print("Client received OBJ HYPER from NetID: %08x\n", msg->NetID());

    NetObjHyper obj_hyper;
    obj_hyper.Unpack(msg->Data());

    NetPlayer* player = FindPlayerByObjID(obj_hyper.GetObjID());
    if (player && player->GetShip())
    player->DoObjHyper(&obj_hyper);
}

void
NetGameClient::DoObjTarget(NetMsg* msg)
{
    if (!msg) return;

    NetObjTarget obj_target;
    obj_target.Unpack(msg->Data());

    NetPlayer* player = FindPlayerByObjID(obj_target.GetObjID());
    if (player)
    player->DoObjTarget(&obj_target);
}

void
NetGameClient::DoObjEmcon(NetMsg* msg)
{
    if (!msg) return;

    NetObjEmcon obj_emcon;
    obj_emcon.Unpack(msg->Data());

    NetPlayer* player = FindPlayerByObjID(obj_emcon.GetObjID());
    if (player)
    player->DoObjEmcon(&obj_emcon);
}

// +--------------------------------------------------------------------+

void
NetGameClient::DoSysDamage(NetMsg* msg)
{
    if (!msg) return;

    NetSysDamage sys_damage;
    sys_damage.Unpack(msg->Data());

    NetPlayer* player = FindPlayerByObjID(sys_damage.GetObjID());
    if (player && player->GetShip())
    player->DoSysDamage(&sys_damage);
}

void
NetGameClient::DoSysStatus(NetMsg* msg)
{
    if (!msg) return;

    NetSysStatus sys_status;
    sys_status.Unpack(msg->Data());

    NetPlayer* player = FindPlayerByObjID(sys_status.GetObjID());
    if (player && player->GetShip())
    player->DoSysStatus(&sys_status);
}

// +--------------------------------------------------------------------+

void
NetGameClient::DoElemCreate(NetMsg* msg)
{
    if (!msg) return;

    NetElemCreate elem_create;
    elem_create.Unpack(msg->Data());

    const char* elem_name = elem_create.GetName().data();

    ::Print("NetGameClient::DoElemCreate name: %s iff: %d type %s\n",
    elem_name,
    elem_create.GetIFF(),
    Mission::RoleName(elem_create.GetType()));

    Sim*     sim   = Sim::GetSim();
    Element* elem  = sim->FindElement(elem_name);
    if (elem) {
        ::Print("  element '%' already exists - ignored\n", elem_name);
        return;
    }

    elem = sim->CreateElement(elem_name,
    elem_create.GetIFF(),
    elem_create.GetType());

    int*     load     = elem_create.GetLoadout();
    int*     slots    = elem_create.GetSlots();
    int      squadron = elem_create.GetSquadron();
    int      code     = elem_create.GetObjCode();
    Text     target   = elem_create.GetObjective();
    bool     alert    = elem_create.GetAlert();
    bool     active   = elem_create.GetInFlight();

    elem->SetIntelLevel(elem_create.GetIntel());
    elem->SetLoadout(load);

    if (code > Instruction::RTB || target.length() > 0) {
        Instruction* obj  = new(__FILE__,__LINE__) Instruction(code, target);
        elem->AddObjective(obj);
    }

    Ship* carrier = sim->FindShip(elem_create.GetCarrier());
    if (carrier) {
        elem->SetCarrier(carrier);

        Hangar* hangar = carrier->GetHangar();
        if (hangar) {
            Text squadron_name = hangar->SquadronName(squadron);
            elem->SetSquadron(squadron_name);

            if (active) {
                for (int i = 0; i < 4; i++) {
                    int slot = slots[i];
                    if (slot > -1) {
                        hangar->GotoActiveFlight(squadron, slot, elem, load);
                    }
                }
            }

            else {
                FlightDeck* deck   = 0;
                int         queue  = 1000;

                for (int i = 0; i < carrier->NumFlightDecks(); i++) {
                    FlightDeck* d = carrier->GetFlightDeck(i);

                    if (d && d->IsLaunchDeck()) {
                        int dq = hangar->PreflightQueue(d);

                        if (dq < queue) {
                            queue = dq;
                            deck  = d;
                        }
                    }
                }

                for (int i = 0; i < 4; i++) {
                    int slot = slots[i];
                    if (slot > -1) {
                        hangar->GotoAlert(squadron, slot, deck, elem, load, !alert);
                    }
                }
            }
        }
    }
}

void
NetGameClient::DoShipLaunch(NetMsg* msg)
{
    if (!msg) return;

    NetShipLaunch ship_launch;
    ship_launch.Unpack(msg->Data());

    Sim*  sim      = Sim::GetSim();
    int   squadron = ship_launch.GetSquadron();
    int   slot     = ship_launch.GetSlot();

    Ship* carrier = FindShipByObjID(ship_launch.GetObjID());

    if (carrier) {
        Hangar* hangar = carrier->GetHangar();

        if (hangar) {
            hangar->Launch(squadron, slot);
        }
    }
}

void
NetGameClient::DoWepTrigger(NetMsg* msg)
{
    if (!msg) return;

    NetWepTrigger trigger;
    trigger.Unpack(msg->Data());

    NetPlayer* player = FindPlayerByObjID(trigger.GetObjID());
    if (player)
    player->DoWepTrigger(&trigger);
}

void
NetGameClient::DoWepRelease(NetMsg* msg)
{
    if (!msg) return;

    NetWepRelease release;
    release.Unpack(msg->Data());

    NetPlayer* player = FindPlayerByObjID(release.GetObjID());
    if (player) {
        player->DoWepRelease(&release);
    }

    else {
        Ship* shooter = FindShipByObjID(release.GetObjID());
        if (shooter) {
            int   index = release.GetIndex();
            DWORD tgtid = release.GetTgtID();
            DWORD wepid = release.GetWepID();
            int   subid = release.GetSubtarget();
            bool  decoy = release.GetDecoy();
            bool  probe = release.GetProbe();

            Weapon* w = 0;

            if (decoy)        w = shooter->GetDecoy();
            else if (probe)   w = shooter->GetProbeLauncher();
            else              w = shooter->GetWeaponByIndex(index);

            if (w && !w->IsPrimary()) {
                SimObject*  target = FindShipByObjID(tgtid);
                System*     subtgt = 0;

                if (target) {
                    if (subid >= 0) {
                        Ship* tgt_ship = (Ship*) target;
                        subtgt = tgt_ship->Systems().at(subid);
                    }
                }
                else {
                    target = FindShotByObjID(tgtid);
                }

                Shot* shot = w->NetFireSecondary(target, subtgt, wepid);

                if (shot && shot->IsDrone()) {
                    if (probe)
                    shooter->SetProbe((Drone*) shot);

                    else if (decoy)
                    shooter->AddActiveDecoy((Drone*) shot);
                }
            }
        }
    }
}

void
NetGameClient::DoNavData(NetMsg* msg)
{
    if (!msg) return;

    NetNavData nav_data;
    nav_data.Unpack(msg->Data());

    Element* elem = sim->FindElement(nav_data.GetElem());
    Ship*    ship = FindShipByObjID(nav_data.GetObjID());

    if (elem) {
        if (nav_data.IsAdd()) {
            Instruction* navpt = new(__FILE__,__LINE__) Instruction(*nav_data.GetNavPoint());
            Instruction* after = 0;
            int          index = nav_data.GetIndex();

            if (index >= 0 && index < elem->GetFlightPlan().size())
            after = elem->GetFlightPlan().at(index);

            elem->AddNavPoint(navpt, after, false);
        }

        else {
            Instruction* navpt = nav_data.GetNavPoint();
            Instruction* exist = 0;
            int          index = nav_data.GetIndex();

            if (navpt && index >= 0 && index < elem->GetFlightPlan().size()) {
                exist = elem->GetFlightPlan().at(index);

                *exist = *navpt;
            }
        }
    }
}

void
NetGameClient::DoNavDelete(NetMsg* msg)
{
    if (!msg) return;

    NetNavDelete nav_delete;
    nav_delete.Unpack(msg->Data());

    Element* elem = sim->FindElement(nav_delete.GetElem());
    Ship*    ship = FindShipByObjID(nav_delete.GetObjID());

    if (elem) {
        int index = nav_delete.GetIndex();

        if (index < 0) {
            elem->ClearFlightPlan(false);
        }

        else if (index < elem->FlightPlanLength()) {
            Instruction* npt = elem->GetFlightPlan().at(index);
            elem->DelNavPoint(npt, false);
        }
    }
}

void
NetGameClient::DoWepDestroy(NetMsg* msg)
{
    if (!msg) return;

    NetWepDestroy destroy;
    destroy.Unpack(msg->Data());

    Shot* shot = FindShotByObjID(destroy.GetObjID());
    if (shot) {
        if (shot->IsBeam())
        ::Print("NetGameClient::DoWepDestroy shot '%s'\n", shot->Name());

        shot->Destroy();
    }
}

void
NetGameClient::DoCommMsg(NetMsg* msg)
{
    if (!msg) return;

    NetCommMsg comm_msg;
    comm_msg.Unpack(msg->Data());

    NetPlayer* player = FindPlayerByObjID(comm_msg.GetObjID());
    if (player)
    player->DoCommMessage(&comm_msg);
}

void
NetGameClient::DoChatMsg(NetMsg* msg)
{
    if (!msg) return;

    NetChatMsg chat_msg;
    chat_msg.Unpack(msg->Data());

    Text name = chat_msg.GetName();
    if (name.length() < 1)
    name = Game::GetText("NetGameClient.chat.unknown");

    HUDView::Message("%s> %s", name.data(), chat_msg.GetText().data());
}

void
NetGameClient::DoSelfDestruct(NetMsg* msg)
{
    if (!msg) return;

    NetSelfDestruct self_destruct;
    self_destruct.Unpack(msg->Data());

    Ship* ship = FindShipByObjID(self_destruct.GetObjID());
    if (ship) {
        ship->InflictNetDamage(self_destruct.GetDamage());
    }
}

// +--------------------------------------------------------------------+

void
NetGameClient::Send()
{
    DWORD time = Game::GameTime();

    // don't flood the network...
    if (time - last_send_time < MIN_NET_FRAME)
    return;

    last_send_time = time;

    if (local_player && objid && server_id) {
        double r, p, y;
        local_player->Cam().Orientation().ComputeEulerAngles(r,p,y);

        NetObjLoc obj_loc;

        obj_loc.SetObjID(objid);
        obj_loc.SetLocation(local_player->Location());
        obj_loc.SetVelocity(local_player->Velocity());
        obj_loc.SetOrientation(Point(r,p,y));
        obj_loc.SetThrottle(local_player->Throttle() > 10);
        obj_loc.SetAugmenter(local_player->Augmenter());
        obj_loc.SetGearDown(local_player->IsGearDown());

        Shield* shield = local_player->GetShield();
        if (shield)
        obj_loc.SetShield((int) shield->GetPowerLevel());
        else
        obj_loc.SetShield(0);

        BYTE* obj_loc_data = obj_loc.Pack();

        link->SendMessage(server_id, obj_loc_data, NetObjLoc::SIZE);
    }
}

// +--------------------------------------------------------------------+

void
NetGameClient::SendData(NetData* net_data)
{
    if (!net_data || !server_id)
    return;

    if (local_player || net_data->Type() < 0x20) {
        BYTE* data  = net_data->Pack();
        BYTE  flags = 0;

        if (net_data->Type() >= 0x10)
        flags |= NetMsg::RELIABLE;

        link->SendMessage(server_id, data, net_data->Length(), flags);
    }
}

// +--------------------------------------------------------------------+

void
NetGameClient::ExecFrame()
{
    if (local_player) {
        if (local_player->GetObjID() == 0) {
            SendJoinRequest();
        }

        else if (active) {
            // check health of server:
            NetPeer* server_peer = link->FindPeer(server_id);
            if (server_peer && (NetLayer::GetUTC() - server_peer->LastReceiveTime() > 15)) {
                NetMsg net_disco(0, NET_DISCONNECT, 0, 0, 0);
                DoDisconnect(&net_disco);
            }

            // if server is still there,
            else if (server_peer) {

                // check if any old join announcements still need to be processed:
                ListIter<NetJoinAnnounce> iter = join_backlog;
                while (++iter) {
                    NetJoinAnnounce* join_ann = iter.value();

                    if (DoJoinBacklog(join_ann)) {
                        iter.removeItem();
                        delete join_ann;
                    }
                }
            }
        }
    }

    NetGame::ExecFrame();
}

// +--------------------------------------------------------------------+

bool
NetGameClient::Update(SimObject* obj)
{
    if (obj->Type() == SimObject::SIM_SHIP) {
        Ship* s = (Ship*) obj;
        if (local_player == s)
        local_player = 0;
    }

    return SimObserver::Update(obj);
}

const char*
NetGameClient::GetObserverName() const
{
    return "NetGameClient";
}

// +--------------------------------------------------------------------+

void
NetGameClient::Respawn(DWORD oid, Ship* spawn)
{
    if (!oid || !spawn) return;

    Print("NetGameClient::Respawn(%d, %s)\n", oid, spawn->Name());
    spawn->SetObjID(oid);
    Observe(spawn);

    NetPlayer* p = FindPlayerByObjID(oid);
    if (p)
    p->SetShip(spawn);

    if (objid == oid) {
        Print("  RESPAWN LOCAL PLAYER\n\n");
        local_player = spawn;
    }
}
